Skip to content

feat: require with_environment_subdomain or an explicit with_legacy_domain opt-out - #193

Merged
armando-rodriguez-cko merged 9 commits into
masterfrom
feat/INT-1688-mandatory-subdomain
Aug 31, 2026
Merged

feat: require with_environment_subdomain or an explicit with_legacy_domain opt-out#193
armando-rodriguez-cko merged 9 commits into
masterfrom
feat/INT-1688-mandatory-subdomain

Conversation

@armando-rodriguez-cko

@armando-rodriguez-cko armando-rodriguez-cko commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes the merchant-specific subdomain (MSSD) mandatory. Callers must now call with_environment_subdomain(...), or explicitly opt out with the new with_legacy_domain, which prints a deprecation warning from its first release. Setting both, or neither, raises CheckoutArgumentException at build time. MSSD is no longer beta and non-MSSD usage will be deprecated, so the previous silent fallback to api.checkout.com had to go.

Changes

  • lib/checkout_sdk/abstract_checkout_sdk_builder.rb — the subdomain is held as a string and the EnvironmentSubdomain is built when the configuration is assembled, so with_environment_subdomain no longer has to be called after with_environment; new with_legacy_domain; new validate_environment_settings and requires_environment_subdomain?
  • lib/checkout_sdk/environment_subdomain.rbcreate_url_with_subdomain raises on an invalid subdomain instead of returning the URL unchanged
  • lib/checkout_sdk/previous/checkout_previous_static_keys_sdk_builder.rb — Previous/ABC exempted
  • spec/checkout_sdk_spec.rb — covers all four combinations, an invalid subdomain, and the Previous exemption
  • spec/checkout_sdk/configuration/configuration_spec.rb — the bad-subdomain cases now assert the raise instead of the silent fallback
  • spec/support/domain_configuration.rb (new) + sandbox_test_fixture.rb, the accounts spec and the issuing helper — every client the suite builds now chooses a domain

Fixed along the way

with_environment_subdomain used to build the URLs from whatever environment was set at call time, so calling it before with_environment silently produced the wrong host.

Verification

576 examples, 0 failures, 149 pending. rubocop reports no offences in the lib/ files touched here; the only new offence overall is Metrics/BlockLength in a spec file that already exceeded it.

API Reference

Breaking changes

Yes, two. This needs a major release, classified and versioned when the release is cut.

  1. The merchant-specific subdomain is mandatory for the Default and DefaultOAuth platforms. Code that omitted it and relied on the implicit fallback to api.checkout.com / access.checkout.com now fails at client construction. Migration: set the subdomain, or use the legacy-domain opt-out as a temporary measure. The Previous (ABC) platform is unaffected.
  2. An invalid subdomain now fails instead of being silently ignored. Callers passing a malformed value keep working against the shared host today; after this change they fail fast. This one is easy to miss because it is not what the ticket asked for, so it needs its own line in the release notes.

README

Updated in this PR: a "Subdomain value" section above the Default example, the subdomain added to the configuration samples, and a "Legacy domain (emergency use only)" section at the bottom.

Notes

The suite routes every client it builds through a single helper that uses the shared hosts. Applying the merchant-specific subdomain there looked better, since it is the path merchants are being moved to, but the sandbox OAuth clients are not provisioned for it: .NET CI failed 224 integration tests with invalid_client when the token request went to {subdomain}.access.sandbox.checkout.com. Binding those OAuth clients to the subdomain is a platform task and should land before merchants are told the subdomain is mandatory.

Reference implementation: checkout-sdk-net#590. Tracked as INT-1688.

No version bump here: that happens on master when the release is cut, per the release workflow.


Review follow-ups (2026-08-31)

Breaking changes, complete list:

  1. The merchant-specific subdomain is now required; building without it (and without the legacy opt-out) throws.
  2. An invalid subdomain now throws instead of being silently ignored.
  3. The environment_subdomain attr_accessor rename drops the generated environment_subdomain= writer, source-breaking for subclassers.
    Behaviour note: host resolution is now deferred to build time, which also fixes a latent order-dependence bug where setting the subdomain before the environment produced the wrong host.

Deprecation signal: runtime warn to stderr.

Option A applied (2026-08-31): an explicit OAuth authorization URI and the environment subdomain are now mutually exclusive at build time; the README OAuth example no longer sets an authorization URI, subdomain-only is the documented path.

@agent-wall-e

agent-wall-e Bot commented Aug 10, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:344>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 12


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 10, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope344>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 10, 2026

Copy link
Copy Markdown

🔵 Advisory review: Sound, but needs your judgement

This PR needs a human approval. The code itself reads as correct; whether it should land depends on context I don't have.

This PR enforces mandatory merchant-specific subdomains for Default/OAuth platforms with a legacy-domain opt-out escape hatch, and the implementation looks technically correct — but the test suite itself universally opts out of the very feature being made mandatory, which is a consequential deployment/readiness question.

For you to decide

  • The entire integration test suite (sandbox_test_fixture.rb, accounts_integration_spec.rb, issuing_helper.rb) uses with_legacy_domain rather than with_environment_subdomain, meaning the enforcement change is never exercised in CI against real sandbox credentials — a human needs to decide if that's acceptable for a release that claims MSSD is now mandatory.
  • The PR description explicitly acknowledges that sandbox OAuth clients are not yet provisioned for MSSD (invalid_client on token requests), which means the mandatory enforcement is being shipped before the platform-side prerequisite is ready — a reviewer should confirm whether this sequencing is intentional and safe for existing customers.
  • The validate_environment_settings method is called at the end of build but returns nil (falls through after the guard clauses without returning self or the built object) — callers chaining on the result of build would get nil instead of the SDK instance; this needs to be verified against the full build method to confirm the return value is correct.
  • The environment_subdomain accessor builds a new EnvironmentSubdomain instance on every call, meaning repeated access (e.g. during configuration assembly) reconstructs and re-validates the object each time — likely harmless but potentially surprising.
  • The requires_environment_subdomain? override exempting Previous/ABC is only added to CheckoutPreviousStaticKeysSdkBuilder, but if there is a CheckoutPreviousOAuthSdkBuilder (not visible in this truncated diff) it would also need the override — a reviewer should confirm all Previous builder subclasses are covered.
  • The with_legacy_domain deprecation warning fires at build time (when the method is called), so every process startup and every test run touching with_legacy_domain will emit the warning — this is noisy in test output and should be a conscious choice.
  • README correctly documents the breaking change and the escape hatch, and the doc and code are consistent with each other on the constraint (exactly one of subdomain or legacy domain, both/neither raise).

⚠️ The diff was too large to read in full, so this review covers only part of the change.


This is not an approval. wall-e cannot auto-approve this PR — it is an opinion to help whoever does. Advisory review · us.anthropic.claude-sonnet-4-6 · wall-e 2026.06.19-02

@agent-wall-e

agent-wall-e Bot commented Aug 11, 2026

Copy link
Copy Markdown

🔴 Risk Classification: MAJOR

Approval route: AI Review + Human Approval Required
Rollback controls: Change-freeze window + documented rollback plan

Classification reasons

  • security_sensitive_path:.github/workflows/build-master.yml
  • security_sensitive_path:.github/workflows/build-pull-request.yml
  • security_sensitive_path:.github/workflows/build-release.yml

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 15


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 11, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
security_sensitive_path.github/workflows/build-master.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).
security_sensitive_path.github/workflows/build-pull-request.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).
security_sensitive_path.github/workflows/build-release.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 12, 2026

Copy link
Copy Markdown

🔴 Risk Classification: MAJOR

Approval route: AI Review + Human Approval Required
Rollback controls: Change-freeze window + documented rollback plan

Classification reasons

  • security_sensitive_path:.github/workflows/build-master.yml
  • security_sensitive_path:.github/workflows/build-pull-request.yml
  • security_sensitive_path:.github/workflows/build-release.yml

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 14


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 12, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
security_sensitive_path.github/workflows/build-master.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).
security_sensitive_path.github/workflows/build-pull-request.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).
security_sensitive_path.github/workflows/build-release.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 12, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:273>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 9


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 12, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope273>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

…omain opt-out

The merchant-specific subdomain is how merchants should reach the API, but it
was optional and an unset value silently fell back to api.checkout.com, so a
forgotten subdomain looked exactly like a deliberate opt-out and the SDK could
not warn about either. Callers must now choose: call with_environment_subdomain,
or with_legacy_domain, which prints a deprecation warning from its first
release. Both, or neither, raises CheckoutArgumentException.

An invalid subdomain now raises instead of being quietly ignored, which is a
second breaking change: callers passing a malformed value are currently served
by the shared host and never find out.

with_environment_subdomain no longer needs with_environment to be set first,
since the EnvironmentSubdomain is now built when the configuration is assembled.

The Previous (ABC) platform predates merchant-specific subdomains and stays
exempt via requires_environment_subdomain?.

Specs route clients through Helpers::DomainConfiguration, which uses the shared
hosts: the sandbox OAuth clients are not provisioned for the subdomain, so
applying it makes every client_credentials request return invalid_client.

Mirrors checkout-sdk-net#590. Refs INT-1688.
The suite could only run against the shared hosts, so the subdomain path this PR
makes mandatory had no integration coverage. Reviewers flagged that on every SDK,
and it is the right thing to flag.

The domain helper now has two modes. Default is unchanged, the shared hosts,
because the sandbox OAuth clients are not provisioned for the subdomain and the
token request returns invalid_client. Set CHECKOUT_TEST_USE_SUBDOMAIN=true and the
suite runs against CHECKOUT_MERCHANT_SUBDOMAIN instead, so once sandbox is
provisioned like production it is a one-line change in the workflows, already
wired and documented, rather than a rewrite of every fixture.

The switch is deliberately separate from CHECKOUT_MERCHANT_SUBDOMAIN, which CI
already exports: provisioning should drive the behaviour, not the presence of a
secret.
Versions are bumped on master during the release, not in a feature branch, per
the release workflow. This branch should carry only the change itself; the major
bump is classified and applied when the release is cut.
Two problems with the previous approach. It needed a new variable in 21 workflow
files, which is not viable without access to create secrets. And it wrapped the
builder chain in a configureDomain helper that is not part of the public API, so
the tests stopped looking like the code a merchant would actually write.

Every fixture now calls the real opt-out inline, in the chain, with a comment
saying why: the sandbox OAuth clients are not provisioned for the merchant-specific
subdomain, so the token request comes back invalid_client. When sandbox is
provisioned, those calls become the subdomain setter.

The unit tests covering all four combinations are untouched: they already used the
public API directly.
@armando-rodriguez-cko
armando-rodriguez-cko force-pushed the feat/INT-1688-mandatory-subdomain branch from c072be4 to 7d77416 Compare August 27, 2026 17:50
@agent-wall-e

agent-wall-e Bot commented Aug 27, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:275>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 10


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 27, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope275>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

Comment thread lib/checkout_sdk/version.rb Outdated
@agent-wall-e

agent-wall-e Bot commented Aug 28, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:273>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 9


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 28, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope273>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@armando-rodriguez-cko
armando-rodriguez-cko requested a review from a team August 28, 2026 16:12
…th client

The dedicated sandbox clients are not provisioned for the merchant
subdomain; the default client now carries every scope the suites need.
@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:275>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 9


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope275>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:273>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 9


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope273>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

david-ruiz-cko
david-ruiz-cko previously approved these changes Aug 31, 2026
- Anchor the subdomain pattern with \A...\z so subdomains containing
  trailing or embedded newlines are rejected, and cover both cases with
  unit specs
- Validate the environment settings before the key validation, matching
  the other SDKs
- Build the environment-subdomain object once per build instead of
  recomputing it on every access
- Initialise the legacy-domain flag in the constructor so it is never
  read before assignment
- Reword the subdomain guidance to 'typically your client ID excluding
  the cli_ prefix' in the error messages
- Use the CHECKOUT_MERCHANT_SUBDOMAIN env var for the static-keys
  integration fixture; keep the legacy-domain opt-out only for OAuth
  clients, citing the sandbox OAuth provisioning gap
- Document that Private Link merchants use their pl- prefixed subdomain
@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:317>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 11


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope317>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:367>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 11


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope367>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@sonarqubecloud

Copy link
Copy Markdown

@armando-rodriguez-cko
armando-rodriguez-cko merged commit 9d7f423 into master Aug 31, 2026
5 checks passed
@armando-rodriguez-cko
armando-rodriguez-cko deleted the feat/INT-1688-mandatory-subdomain branch August 31, 2026 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants